Dart 健全的空安全: 技术预览版 2
Dart 在 Flutter 中扮演着特别的角色——它为开发者提供了热重载等功能,并通过灵活的编译器技术实现移动、桌面和 web 端的多平台应用。我们努力使 Dart 语言能够为 Flutter 应用开发者提供最大的生产力。例如,我们添加了 UI-as-code 语言结构来优化 Dart 语法,以便于编写 Flutter widget 树。
我们于 6 月推出了 Dart 空安全的首个技术预览版。现在则是我们期待已久的另一个重要里程碑: 健全的空安全第二个技术预览版本,其中包含对 Flutter 框架的支持。
Dart 2.10
https://medium.com/dartlang/announcing-dart-2-10-350823952bd5
为什么需要空安全?
Dart 是一种类型安全的语言。即当您拿到某个类型的变量时,编译器可以保证它属于该类型。但是类型安全本身并不能保证变量不为空 (null)。
空值错误十分常见。在 GitHub 上搜索,您会看到数以千计由 Dart 代码中意外的空值导致的问题,更有成千上万的开发者在试图修复这些问题。请您试着找出下面这个应用中的空值问题 (Config 和 WeatherService 是该应用使用的后端服务):
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Get data from services. Note: in a real application,
// these would be async calls, but we're using sync calls
// for simplicity.
final localizedAppName = Config.getAppName();
final temperatures = WeatherService.getTemperatures();
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text(localizedAppName)),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Temperature next 3 days:'),
for (final t in temperatures)
Text(t.round().toString()),
],
),
),
),
);
}
}
class Config {
static String getAppName() { ... }
}
class WeatherService {
static List<double> getTemperatures() { ...}
}
如果 getAppName() 返回 null,则应用一定会出错;在这种情况下,我们会向 AppBar 标题中的 Text widget 传递 null。
但是,还有更多微妙的情况需要考虑: getTemperatures() 也可以返回 null。在这种情况下,for 循环将失败。或者,getTemperatures() 也可能会如预期返回一个列表,但是该列表可能包含 null 值,在这种情况下,我们对 null 调用 round(),这同样也会出错。
空安全特性可在您编写代码时就对其进行验证,从而消除这些问题:
DartPad: 验证代码 https://nullsafety.dartpad.cn/28f1db5ef4401d9e063375e5c58f0f86
有了空安全,您就可以更放心地编写和使用代码。不再需要等到运行时才遭遇空引用错误,在编写代码时您就能看到静态错误提醒。
空安全设计原则
Dart 的空安全支持基于以下三个核心设计原则:
默认不可空。除非您明确告诉 Dart 一个变量是可空的,否则它将一概认为所有变量都不可空。我们之所以这么做,是因为我们看到不可空是 API 中最常见的选择。
可逐步采用。用 Dart 编写的代码数量庞大。您将可以择机且逐步地迁移至空安全。在同一项目中将可以同时包含空安全代码和非空安全代码。我们还将提供工具来帮助您进行迁移。
彻底健全。Dart 提供健全的空安全。这意味着我们可以信任类型系统: 如果类型系统确定某项不为空,则它一定不会为空。这样可实现编译器优化。在将整个项目和依赖都迁移到空安全之后,您将尽享健全空安全的优势: 错误会更少,二进制文件也会更小,执行速度则会更快。
让我们来详细介绍上述这些设计原则。
1. 默认非空
// 在空安全的 Dart 中,以下变量皆不可空。
var widget = Text('Hello');
final status = GetStatus();
String m = '';
Dart 将确保您无法将空值赋予上述任何变量。如果您尝试在一千行之后执行 widget = null,则会触发静态分析错误并看到红色波浪线,程序也将无法编译。
如果您希望变量可空,则可以使用 ?,如下所示:
// 下列变量可空。
Text? t = Text('Hello'); // 之后可被赋予 null 值。
final Status? s = getStatus(); // 方法可以返回 null。
String? n; // 初始即为 null,之后任何时间也可为 null。
// 在函数参数里使用。
void initialize(int? count) {
// It's possible that count is null.
}
// 在函数返回值使用。
static List<double?>? getTemperatures() {
// 可以返回 null,或者 List 里也可以含 null 项。
}
但再次强调,理想情况是您几乎不需要使用 ?。您的大多数类型均不可为空。
void honk(int? loudness) {
if (loudness == null) {
// 没有给出 loudness 值时直接用最大音量告知开发者。
_playSound('error.wav', volume: 11);
return;
}
// Loudness 不为空,直接将其调整(clamp)至可接受的范围。
_playSound('honk.wav', volume: loudness.clamp(0, 11));
}
请注意 Dart 工具在 if 语句之后就已经知道 loudness 变量不会为空。之后我们不必大费周章便能轻松调用 clamp() 方法。这种便利性是通过流程分析 (flow analysis) 来实现的: Dart 分析器会像执行代码一样检查您的代码,自动找出代码中附加的信息。
下面是另一个示例,Dart 可以确定一个变量非空,因为我们总是给它分配一个非空值:
class StatusLine extends StatelessWidget {
final Status status;
StatusLine({this.status: Status.failed});
@override
Widget build(BuildContext context) {
// 这个本地变量不可空,只是未被初始化。
String statusText;
if (status == Status.ok) {
statusText = 'Update succeeded';
} else {
statusText = 'Update failed';
}
// 运行到这里,Dart 就知道 statusText 不为空,
// 因为我们在 if 和 else 里都对其进行了赋值。
// 因此我们可以将其传给 Text widget(该 widget 不接受空值)并且不报错。
return Text(statusText);
}
}
如果您移除上述任何赋值语句 (例如,删除 statusText = 'Update failed'; 一行),则 Dart 将无法保证 statusText 非空: 您将看到一个静态错误,且代码将无法编译。您可以在 DartPad 中体验这种空安全。
体验空安全
https://nullsafety.dartpad.cn/ecc0f87fa5af5cc7ff30d8bd3e3b12e2
2. 可逐步采用
迁移顺序为何如此重要?虽然您可以在迁移依赖项之前先处理一些代码迁移工作,但如果依赖项在迁移期间更改了其 API,那么您将面临重新执行迁移的风险。进入 beta 测试版后,我们将提供工具来帮助您确定哪些依赖项已迁移。如果您是 package 的作者,那么为了避免破坏 API,请等到所有依赖项都已迁移完毕后再发布空安全版本。
依赖项准备就绪后,您便可以使用我们的迁移工具。该工具会对您所有的现有代码执行分析。迁移工具是交互式的,因此您可以查看该工具的可空性推断结果。如果您对工具给出的结论有异议,则可以添加可空性提示以更改推断。添加迁移提示有可能大幅提升迁移质量。
3. 彻底健全
空安全发布时间表
Flutter 空安全示例
https://github.com/flutter/samples/tree/master/experimental/null_safety
实验性的参数标记
https://dart.cn/tools/experiment-flags
即刻上手体验
您可以立即体验空安全!为了帮助大家快速上手,我们准备了这个空安全版 DartPad。
空安全版 DartPad https://nullsafety.dartpad.cn/
Flutter 空安全示例应用
https://github.com/flutter/samples/tree/master/experimental/null_safety
实验说明
https://github.com/flutter/flutter/wiki/Experimenting-with-null-safety-in-Flutter
腾讯视频链接
https://v.qq.com/x/page/i3126s1tjww.html
Bilibili 视频链接
https://www.bilibili.com/video/BV1TD4y1U7ST/
深入理解空安全 https://dart.cn/null-safety/understanding-null-safety Flutter Day https://space.bilibili.com/64169458/favlist?fid=1025514958
体验空安全特性 https://github.com/flutter/flutter/wiki/Experimenting-with-null-safety-in-Flutter 提交反馈 https://github.com/dart-lang/sdk/issues/new?title=Null%20safety%20feedback:%20[issue%20summary]&labels=NNBD&body=Describe%20the%20issue%20or%20potential%20improvement%20in%20detail%20here
推荐阅读